﻿Imports System.IO
Imports System.Net
Imports System.Net.FtpClient
Imports System.Net.Sockets
Imports SFDL.Core
Imports System.Threading
Imports System.Threading.Tasks
Imports SFDL.LoaderExtensions

' ReSharper disable once CheckNamespace
Public Class ThreadHandler

    Private Shared _log As NLog.Logger = NLog.LogManager.GetLogger("ThreadHandler")
    Private Shared _master_ftp_client_list As New Dictionary(Of Guid, FtpClient)
    Private Shared _list_ftpthreadinfo As New Dictionary(Of Guid, FTPThreadInfo)

    Private _obj_lock_list_ftpthreadInfo As New Object

    Protected Friend Shared Event StopDownloadComplete(ByVal sender As Object, ByVal e As EventArgs)
    Protected Friend Shared Event DownloadCompleted(ByVal sender As Object, ByVal e As EventArgs)
    Protected Friend Shared Event FileDownloadComplete(ByVal sender As Object, ByVal e As FileDownloadCompleteEventArg)

    Private _error_on_start As Boolean = False
    Private _max_bytes_per_second As Integer = 0
    Private _max_kbits As Integer = 0

    Protected Friend ReadOnly Property GetFTPThreadInfo(ByVal _guid As Guid) As FTPThreadInfo
        Get

            If _list_ftpthreadinfo.ContainsKey(_guid) Then
                Return _list_ftpthreadinfo.Item(_guid)
            Else
                Return Nothing
            End If

        End Get
    End Property

    Protected Friend ReadOnly Property IsDownloadStopped(ByVal _guid As Guid) As Boolean
        Get
            If _list_ftpthreadinfo.ContainsKey(_guid) Then
                Return _list_ftpthreadinfo.Item(_guid).DownloadStopped
            Else
                Return False
            End If
        End Get
    End Property

    Public Property MaxDownloadSpeed As Integer
        Set(value As Integer)
            _max_kbits = value
            SetMaxDownloadSpeed(_max_kbits)
        End Set
        Get
            Return _max_kbits
        End Get
    End Property

    Private Sub SetMaxDownloadSpeed(_kbits As Integer)

        If _kbits = 0 Then
            _max_bytes_per_second = 0
        Else

            If Not _list_ftpthreadinfo.Count <= 1 Then
                _max_bytes_per_second = CInt((_kbits * 1024) / _list_ftpthreadinfo.Count)
            Else
                _max_bytes_per_second = CInt((_kbits * 1024))
            End If

        End If

    End Sub

    Private Function GetMasterFTPCLientByIP(ByVal _ip As String, ByVal _username As String, ByVal _port As Integer) As FtpClient

        Dim _rt As FtpClient = Nothing

        For Each _master_client_item In _master_ftp_client_list.Where(Function(_my_item) _my_item.Value.Host.Equals(_ip))

            If _master_client_item.Value.Credentials.UserName.ToLower.Equals(_username.ToLower) And _master_client_item.Value.Port = _port Then
                _rt = _master_client_item.Value
            End If

        Next

        Return _rt

    End Function

    Private Function GetMasterFTPCLientGUIDByIP(ByVal _ip As String, ByVal _username As String, ByVal _port As Integer) As Guid

        Dim _rt As Guid

        For Each _master_client_item In _master_ftp_client_list.Where(Function(_my_item) _my_item.Value.Host.Equals(_ip))

            If _master_client_item.Value.Credentials.UserName.ToLower.Equals(_username.ToLower) And _master_client_item.Value.Port = _port Then
                _rt = _master_client_item.Key
            End If

        Next

        Return _rt

    End Function

    Private Function GetMasterFTPClient(ByVal _guid As Guid) As FtpClient

        Dim _rt As FtpClient = Nothing

        If _master_ftp_client_list.ContainsKey(_guid) Then
            _rt = _master_ftp_client_list(_guid)
        End If

        Return _rt

    End Function

    Private Shared Sub ValidateCertificateEvent(control As FtpClient, e As FtpSslValidationEventArgs)
        e.Accept = True
    End Sub

    Protected Friend Sub CheckForNewThreads()

        Task.Run(Sub() StartDownloads()).ContinueWith(Sub()

                                                          If _error_on_start = True Then
                                                              RaiseEvent DownloadCompleted(Me, New EventArgs)
                                                          End If

                                                      End Sub)


    End Sub

    Protected Friend Sub HandleThreads()

        _master_ftp_client_list = New Dictionary(Of Guid, FtpClient)
        _list_ftpthreadinfo = New Dictionary(Of Guid, FTPThreadInfo)
        _obj_lock_list_ftpthreadInfo = New Object

        Task.Run(Sub() StartDownloads()).ContinueWith(Sub()

                                                          If _error_on_start = True Then
                                                              RaiseEvent DownloadCompleted(Me, New EventArgs)
                                                          End If

                                                      End Sub)

    End Sub

    Private Sub AddFTPThreadInfo(ByVal _ftp_thread_info As FTPThreadInfo)

        Try

            Monitor.Enter(_obj_lock_list_ftpthreadInfo)

            If IsNothing(_ftp_thread_info) Then
                Throw New Exception("FTPThreadInfo is NUll!")
            End If

            If _list_ftpthreadinfo.ContainsKey(_ftp_thread_info.GUID) Then
                Throw New Exception("GUID ist bereits in der Liste!")
            End If

            _list_ftpthreadinfo.Add(_ftp_thread_info.GUID, _ftp_thread_info)

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        Finally
            Monitor.Exit(_obj_lock_list_ftpthreadInfo)
        End Try

    End Sub

    Private Sub RemoveFTPThreadInfo(ByVal _ftp_thread_info As FTPThreadInfo)

        Try

            Monitor.Enter(_obj_lock_list_ftpthreadInfo)

            If IsNothing(_ftp_thread_info) Then
                Throw New Exception("FTPThreadInfo is NUll!")
            End If

            If Not _list_ftpthreadinfo.ContainsKey(_ftp_thread_info.GUID) Then
                Throw New Exception("GUID ist nicht in der Liste vorhanden!")
            End If

            _list_ftpthreadinfo.Remove(_ftp_thread_info.GUID)

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        Finally
            Monitor.Exit(_obj_lock_list_ftpthreadInfo)
        End Try

    End Sub

    Private Sub UpdateFTPThreadInfo(ByVal _ftp_thread_info As FTPThreadInfo)

        Try

            Monitor.Enter(_obj_lock_list_ftpthreadInfo)

            If IsNothing(_ftp_thread_info) Then
                Throw New Exception("FTPThreadInfo is NUll!")
            End If

            If Not _list_ftpthreadinfo.ContainsKey(_ftp_thread_info.GUID) Then
                Throw New Exception("GUID ist nicht in der Liste vorhanden!")
            End If

            _list_ftpthreadinfo.Remove(_ftp_thread_info.GUID)

            _list_ftpthreadinfo.Add(_ftp_thread_info.GUID, _ftp_thread_info)

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        Finally
            Monitor.Exit(_obj_lock_list_ftpthreadInfo)
        End Try

    End Sub

    Private Sub ParseException(ByVal exception As Exception, ByVal _ftp_thread_info As FTPThreadInfo)

        Try

            If IsNothing(_ftp_thread_info) Then
                Throw New Exception("FTPThreadINfo is NUll!")
            End If

            If _ftp_thread_info.DownloadStopped = False Then

                _log.Debug("Download ist nicht gestoppt")

                If Not _ftp_thread_info.RetryCount >= GetSettings.MaxRetry Then

                    UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.Retry)

                    _log.Info("Retry - Starte Download für dieses Item in {0} Sekunden neu", GetSettings.RetryWaitTime)

                    If Not GetSettings.RetryWaitTime = 0 Then
                        Thread.Sleep(GetSettings.RetryWaitTime * 1000) 'Hier warten
                    Else
                        Thread.Sleep(3000) 'Hier warten
                    End If

                    _ftp_thread_info.RetryCount += 1
                    ' _ftp_thread_info.ErrorOccured = False

                    UpdateFTPThreadInfo(_ftp_thread_info)

                    Task.Run(Sub() DownloadFile(_ftp_thread_info))

                Else

                    UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.UnknownError)

                    Try 'CleanUp
                        _log.Info("Entferne File {0} mit der GUID {1} aus der Liste...", _ftp_thread_info.FileItem.FileName, _ftp_thread_info.GUID)
                        RemoveFTPThreadInfo(_ftp_thread_info)
                        DecreaseActiveThreadCounter(_ftp_thread_info.SFDLSession.SFDLSessionName)
                    Catch ex As Exception
                        _log.Error(ex, ex.Message)
                    End Try

                End If

            End If

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        End Try

    End Sub

    Private Sub ParseFTPException(ByVal exception As FtpException, ByVal _ftp_thread_info As FTPThreadInfo)

        Try

            If IsNothing(_ftp_thread_info) Then
                Throw New Exception("FTPThreadINfo is NUll!")
            End If

            If _ftp_thread_info.DownloadStopped = False Then

                _log.Debug("Download ist nicht gestoppt")

                If Not _ftp_thread_info.RetryCount >= GetSettings.MaxRetry Then

                    UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.Retry)

                    _log.Info("Retry - Starte Download für dieses Item in {0} Sekunden neu", GetSettings.RetryWaitTime)

                    If Not GetSettings.RetryWaitTime = 0 Then
                        Thread.Sleep(GetSettings.RetryWaitTime * 1000) 'Hier warten
                    Else
                        Thread.Sleep(3000) 'Hier warten
                    End If

                    _ftp_thread_info.RetryCount += 1
                    '_ftp_thread_info.ErrorOccured = False

                    UpdateFTPThreadInfo(_ftp_thread_info)

                    Task.Run(Sub() DownloadFile(_ftp_thread_info))

                Else

                    If exception.Message.ToLower.Contains("type set to i") Then
                        UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.ConnectionError)
                    Else
                        UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.UnknownError)
                    End If

                    Try 'CleanUp
                        _log.Info("Entferne File {0} mit der GUID {1} aus der Liste...", _ftp_thread_info.FileItem.FileName, _ftp_thread_info.GUID)
                        RemoveFTPThreadInfo(_ftp_thread_info)
                        DecreaseActiveThreadCounter(_ftp_thread_info.SFDLSession.SFDLSessionName)
                    Catch ex As Exception
                        _log.Error(ex, ex.Message)
                    End Try

                End If

            End If

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        End Try

    End Sub

    Private Sub ParseFTPCommandException(ByVal exception As FtpCommandException, ByVal _ftp_thread_info As FTPThreadInfo)

        Try

            If IsNothing(_ftp_thread_info) Then
                Throw New Exception("FTPThreadINfo is NUll!")
            End If

            If _ftp_thread_info.DownloadStopped = False Then

                _log.Debug("Download ist nicht gestoppt")

                If Not _ftp_thread_info.RetryCount >= GetSettings.MaxRetry Then

                    UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.Retry)

                    _log.Info("Retry - Starte Download für dieses Item in {0} Sekunden neu", GetSettings.RetryWaitTime)

                    If Not GetSettings.RetryWaitTime = 0 Then
                        Thread.Sleep(GetSettings.RetryWaitTime * 1000) 'Hier warten
                    Else
                        Thread.Sleep(3000) 'Hier warten
                    End If

                    _ftp_thread_info.RetryCount += 1
                    '_ftp_thread_info.ErrorOccured = False

                    UpdateFTPThreadInfo(_ftp_thread_info)

                    Task.Run(Sub() DownloadFile(_ftp_thread_info))

                Else

                    _log.Info("Retry Maximum bereits ereicht!")

                    Select Case exception.CompletionCode

                        Case "421" ' Service not available, closing control connection. This may be a reply to any command if the service knows it must shut down.

                            If exception.Message.ToString.ToLower.Contains("maximum login limit has been reached.") Then
                                UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.ServerFull)
                            Else
                                UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.ServerDown)
                            End If

                        Case "425" ' Can't open data connection.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.ConnectionError)

                        Case "426" 'Connection closed; transfer aborted.

                            'Download Stopped

                        Case "430" ' Invalid username or password

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.AuthError)

                        Case "434" 'Requested host unavailable.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.ServerDown)

                        Case "450" 'Requested file action not taken.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileNotFound)

                        Case "451" 'Requested action aborted. Local error in processing

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.InternalServerError)

                        Case "452"

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.InternalServerError)

                        Case "501" 'Syntax error in parameters or arguments.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DirectoryNotFound)

                        Case "502" 'Command not implemented.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.InternalServerError)

                        Case "503" 'Bad sequence of commands.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.InternalServerError)

                        Case "504" 'Command not implemented for that parameter

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.InternalServerError)

                        Case "530" 'Not logged in.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.AuthError)

                        Case "550" 'Requested action not taken. File unavailable (e.g., file not found, no access).

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileNotFound)

                        Case "553" ' Requested action not taken. File name not allowed.

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileNotFound)

                        Case Else

                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.UnknownError)

                    End Select

                    Try 'CleanUp
                        _log.Info("Entferne File {0} mit der GUID {1} aus der Liste...", _ftp_thread_info.FileItem.FileName, _ftp_thread_info.GUID)
                        RemoveFTPThreadInfo(_ftp_thread_info)
                        DecreaseActiveThreadCounter(_ftp_thread_info.SFDLSession.SFDLSessionName)
                    Catch ex As Exception
                        _log.Error(ex, ex.Message)
                    End Try

                End If

            Else
                _log.Info("Download wird gerade gestoppt - tue nichts")
            End If

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        End Try

    End Sub

    Private Sub StartDownloads()

        Dim _download_items As New List(Of OLV_Item)
        Dim _tools As New Tools

        Try

            _error_on_start = False

            If GetDownloadStopInProgress Then
                Throw New DownloadCompletedException("Download wird gerade gestoppt - Starte keinen neuen Threads!")
            End If

            _log.Info("Frage nach welche Items heruntergeladen werden sollen....")

            Dim _task As Task(Of List(Of OLV_Item))

            SetMaxDownloadSpeed(_max_kbits)

            _task = Task.Run(Function()

                                 Return QueryDownloadItems(_list_ftpthreadinfo.Count)

                             End Function)

            _download_items = _task.Result

            _task.Dispose()

            If _download_items.Count = 0 Then
                _log.Info("Es wurden keine Items zum herunterladen gefunden!")
                Throw New DownloadCompletedException
            End If

            For Each _dl_item As OLV_Item In _download_items

                Dim _sfdl_session As New SFDLSession
                Dim _ftp_client As FtpClient
                Dim _ftp_thread_info As New FTPThreadInfo
                Dim _ftp_client_guid As Guid

                Try

                    _sfdl_session = GetSFDLSessionByKey(_dl_item.SFDLSessionName)

                    _log.Info("Prüfe ob es bereits eine FTP Verbindung existiert....")

                    _ftp_client = GetMasterFTPCLientByIP(_sfdl_session.SFDLFile.ConnectionInfo.Host, _sfdl_session.SFDLFile.ConnectionInfo.Username, _sfdl_session.SFDLFile.ConnectionInfo.Port)

                    If Not IsNothing(_ftp_client) Then

                        _log.Info("Passende FTP Verbindung gefunden!")

                        _ftp_client_guid = GetMasterFTPCLientGUIDByIP(_sfdl_session.SFDLFile.ConnectionInfo.Host, _sfdl_session.SFDLFile.ConnectionInfo.Username, _sfdl_session.SFDLFile.ConnectionInfo.Port)

                    Else
                        _log.Info("Keine Passende FTP Verbindung gefunden - Erstelle neue Verbindung")

                        _ftp_client = New FtpClient

                        With _ftp_client

                            If _sfdl_session.SFDLFile.ConnectionInfo.AuthRequired Then
                                .Credentials = New NetworkCredential(_sfdl_session.SFDLFile.ConnectionInfo.Username, _sfdl_session.SFDLFile.ConnectionInfo.Password)
                            Else
                                .Credentials = New NetworkCredential("anonymous", "anonymous@sfdl.net")
                            End If

                            .DataConnectionType = _sfdl_session.SFDLFile.ConnectionInfo.DataConnectionType
                            .EncryptionMode = _sfdl_session.SFDLFile.ConnectionInfo.EncryptionMode
                            .Host = _sfdl_session.SFDLFile.ConnectionInfo.Host
                            .Port = _sfdl_session.SFDLFile.ConnectionInfo.Port
                            .Encoding = _tools.ConvertCharacterEncoding(_sfdl_session.SFDLFile.ConnectionInfo.CharacterEncoding)
                            .InternetProtocolVersions = FtpIpVersion.IPv4

                            If _sfdl_session.SFDLFile.ConnectionInfo.ForceSingleConnection = True Then
                                _log.Info("Erzwinge Einzelverbindung!")
                                .EnableThreadSafeDataConnections = False
                            End If

                            If Not .EncryptionMode = FtpEncryptionMode.Explicit Then 'FTPS Workaround
                                .DataConnectionEncryption = False
                            Else
                                .DataConnectionEncryption = True
                            End If

                        End With

                        AddHandler _ftp_client.ValidateCertificate, AddressOf ValidateCertificateEvent

                        _log.Info("Verbindung erstellt!")

                        _ftp_client_guid = Guid.NewGuid

                        _master_ftp_client_list.Add(_ftp_client_guid, _ftp_client)

                    End If

                    _log.Info("Prepariere DownloadItem...")

                    _log.Info("Setze ThreadInfos für File:{0}", _dl_item.FileName)

                    _ftp_thread_info.DownloadStopped = False
                    _ftp_thread_info.FileItem = _dl_item
                    _ftp_thread_info.GUID = Guid.NewGuid
                    _ftp_thread_info.RetryCount = 0
                    _ftp_thread_info.SFDLSession = _sfdl_session
                    _ftp_thread_info.FTPClientGUID = _ftp_client_guid

                    'Verbindung herstellen

                    _log.Info("Baue nun die verbindung auf...")

                    If _ftp_client.IsConnected Then
                        _log.Info("Verbindung hergestellt!")
                    Else
                        _ftp_client.Connect()
                    End If

                    'Thread Starten

                    _log.Info("Starte Thread für File {0}", _dl_item.FileName)

                    AddFTPThreadInfo(_ftp_thread_info)

                    SetMaxDownloadSpeed(_max_kbits)

                    Task.Run(Sub() DownloadFile(_ftp_thread_info))

                Catch ex As FtpCommandException
                    'UpdateAllSessionItems(_sfdl_session.SFDLSessionName, UpdateItemEventType.ConnectionError)
                    DecreaseActiveThreadCounter(_sfdl_session.SFDLSessionName)
                    _error_on_start = True

                Catch ex As FtpException
                    ParseFTPException(ex, _ftp_thread_info)
                    DecreaseActiveThreadCounter(_sfdl_session.SFDLSessionName)
                    _error_on_start = True

                Catch ex As SocketException
                    UpdateItem(_dl_item, UpdateItemEventType.ConnectionError)
                    DecreaseActiveThreadCounter(_sfdl_session.SFDLSessionName)
                    _error_on_start = True

                Catch ex As TimeoutException
                    UpdateItem(_dl_item, UpdateItemEventType.ConnectionError)
                    DecreaseActiveThreadCounter(_sfdl_session.SFDLSessionName)
                    _error_on_start = True

                Catch ex As Exception
                    _log.Error("Fehler beim starten des Thread für File {0}", _dl_item.FileName)
                    UpdateItem(_dl_item, UpdateItemEventType.UnknownError)
                    DecreaseActiveThreadCounter(_sfdl_session.SFDLSessionName)
                    _error_on_start = True
                End Try

            Next

        Catch ex As DownloadCompletedException

            Try

                Monitor.Enter(_obj_lock_list_ftpthreadInfo)

                If _list_ftpthreadinfo.Count = 0 Then

                    _log.Info("List FTP Verbindungen ist leer - Ich trenne mal die Verbindung...")

                    Try

                        For Each _master_ftp_client_item In _master_ftp_client_list
                            If _master_ftp_client_item.Value.IsConnected Then
                                _master_ftp_client_item.Value.Disconnect()
                            End If
                        Next

                        For Each _master_ftp_client_item In _master_ftp_client_list
                            _master_ftp_client_item.Value.Dispose()
                        Next

                        _log.Info("Verbinung getrennt!")

                    Catch ex_disconnect As Exception
                        _log.Error(ex, "Disconnect Fehler: {0}", ex.Message)
                    Finally

                        If _list_ftpthreadinfo.Count = 0 Then
                            _list_ftpthreadinfo.Clear()
                            _master_ftp_client_list.Clear()
                        Else
                            _log.Warn("FTPThreadInfo List ist nocht nicht leer!")
                        End If

                    End Try

                    RaiseEvent DownloadCompleted(Me, New EventArgs)

                End If

            Finally
                Monitor.Exit(_obj_lock_list_ftpthreadInfo)
            End Try

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        End Try

    End Sub

    Private Sub DownloadFile(ByVal _ftp_thread_info As FTPThreadInfo)

        Dim _local_file_path As String = String.Empty
        Dim _local_file_info As FileInfo
        Dim _settings As New Settings
        Dim _tools As New Tools
        Dim _ftp_client As New FtpClient
        Dim _disk_free_space As Long

        Try

            If _ftp_thread_info Is Nothing Then
                Throw New Exception("FTPThreadInfo is Null!")
            End If

            _settings = GetSettings

            If _settings Is Nothing Then
                Throw New Exception("Settings is Null!")
            End If

            _ftp_client = GetMasterFTPClient(_ftp_thread_info.FTPClientGUID)

            If _ftp_client Is Nothing Then
                Throw New Exception("FTPClient is NUll!")
            End If

            _log.Info("Ermittle lokalen Dateipfad...")

            _local_file_path = GetDownloadFilePath(GetSettings, _ftp_thread_info.SFDLSession, _ftp_thread_info.FileItem)

            If String.IsNullOrWhiteSpace(_local_file_path) Then
                Throw New Exception("Dateipfad ist leer!")
            End If

            If _local_file_path.Length >= 255 Then
                Throw New FileNameTooLongException("Dateipfad ist zu lang! - Kann Datei nicht schreiben!")
            End If

            _log.Info("Dateipfad ermittelt!")

            _ftp_client.Encoding = _tools.ConvertCharacterEncoding(_ftp_thread_info.SFDLSession.SFDLFile.ConnectionInfo.CharacterEncoding)

            If _ftp_thread_info.FileItem.FileSize <= 0 Then
                Try
                    _log.Warn("Komisch....Angegebene Item Datei Größe ist 0 - aktualisiere Dateigröße....")
                    _ftp_thread_info.FileItem.FileSize = _ftp_client.GetFileSize(_ftp_thread_info.FileItem.FileFullPath)
                    _log.Info("Dateigröße erfolgreich aktualisiert!")
                Catch ex As Exception
                    _log.Warn(ex, "Dateigröße konnte leider nicht aktualisiert werden!")
                End Try
            End If

            _disk_free_space = My.Computer.FileSystem.GetDriveInfo(IO.Path.GetPathRoot(GetSettings.DownloadDirectory)).AvailableFreeSpace

            _log.Info("Freier Speicherplatz: {0}", _disk_free_space)

            If _ftp_thread_info.FileItem.FileSize > _disk_free_space Then
                Throw New Exception("Zu wenig Speicherplatz!")
            End If


            _local_file_info = New FileInfo(_local_file_path)
            _ftp_thread_info.LocalFileInfo = _local_file_info

            If IO.File.Exists(_local_file_path) = True And GetSettings.ExistingFileHandling = ExistingFileHandling.ResumeFile Then

                _log.Info("Lokale Datei existiert - Resume Mode")

                _ftp_client.BeginOpenRead(_ftp_thread_info.FileItem.FileFullPath, _local_file_info.Length, AddressOf OpenReadCallback, _ftp_thread_info)

            Else
                _log.Info("Datei existiert nicht - Starte normalen Download")

                _ftp_client.BeginOpenRead(_ftp_thread_info.FileItem.FileFullPath, AddressOf OpenReadCallback, _ftp_thread_info)

            End If

        Catch ex As NotEnoughFreeDiskSpaceException
            _log.Error(ex, ex.Message)
            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.NotEnoughDiskSpace)

        Catch ex As FileNameTooLongException
            _log.Warn(ex, ex.Message)
            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileNameTooLong)

        Catch ex As Exception
            _log.Error(ex, ex.Message)
            _ftp_thread_info.ErrorOccured = True
            ParseException(ex, _ftp_thread_info)
        End Try

    End Sub

    Private Sub OpenReadCallback(ByVal ar As IAsyncResult)

        Dim _ftp_thread_info As New FTPThreadInfo
        Dim _ftp_client As New FtpClient
        Dim _filemode As IO.FileMode
        'Dim _ftp_read_stream As Stream = Nothing

        'IO Stream Variablen
        Const Length As Integer = 256
        Dim buffer As [Byte]()
        Dim bytesRead As Integer = 0
        Dim bytestotalread As Integer = 0
        Dim _starttime As DateTime = DateTime.Now

        Dim _percent_downloaded As Integer = 0
        Dim _current As Long = 0
        Dim _ctime As TimeSpan
        Dim elapsed As TimeSpan
        Dim bytesPerSec As Integer = 0

        Try

            If IsNothing(ar.AsyncState) Then
                Throw New Exception("AsyncState is Nothing!")
            End If

            _ftp_thread_info = TryCast(ar.AsyncState, FTPThreadInfo)
            _ftp_client = GetMasterFTPClient(_ftp_thread_info.FTPClientGUID)
            _ftp_thread_info.ErrorOccured = False

            Using _ftp_read_stream As Stream = _ftp_client.EndOpenRead(ar)

                If IO.File.Exists(_ftp_thread_info.LocalFileInfo.FullName) = True And GetSettings.ExistingFileHandling = ExistingFileHandling.ResumeFile Then
                    _filemode = FileMode.Append
                Else
                    _filemode = FileMode.Create
                End If

                _log.Info("FileMode: {0}", _filemode.ToString)

                buffer = New Byte(8192) {}
                bytesRead = _ftp_read_stream.Read(buffer, 0, buffer.Length)

                Using _local_write_stream As New FileStream(_ftp_thread_info.LocalFileInfo.FullName, _filemode, FileAccess.Write, FileShare.None, 8192, False)

                    While bytesRead > 0 And IsDownloadStopped(_ftp_thread_info.GUID) = False

                        Dim _tmp_percent_downloaded As Double = 0
                        Dim _new_perc As Integer = 0
                        Dim _download_speed As String = String.Empty

                        If _ftp_read_stream.Length > 0 Then

                            _local_write_stream.Write(buffer, 0, bytesRead)

                            bytesRead = _ftp_read_stream.Read(buffer, 0, Length)
                            bytestotalread += bytesRead

                            elapsed = DateTime.Now.Subtract(_starttime)

                            _tmp_percent_downloaded = CDbl(_local_write_stream.Position) / CDbl(_ftp_read_stream.Length)
                            _new_perc = CInt(_tmp_percent_downloaded * 100)

                            bytesPerSec = CInt(If(elapsed.TotalSeconds < 1, bytestotalread, bytestotalread / elapsed.TotalSeconds))

                        End If

                        If _new_perc <> _percent_downloaded Then 'Nicht jedesmal Updaten

                            Dim _tmp_speed As Double

                            _percent_downloaded = _new_perc

                            _current = _ftp_read_stream.Position
                            _ctime = DateTime.Now.Subtract(_starttime)

                            _tmp_speed = Math.Round(bytesPerSec / 1024, 2)

                            If _tmp_speed >= 1024 Then
                                _download_speed = Math.Round(_tmp_speed / 1024, 2) & " MB/s"
                            Else
                                _download_speed = _tmp_speed & " KB/s"
                            End If

                            _ftp_thread_info.FileItem.SizeDownloaded = _local_write_stream.Position

                            UpdateItemProgress(_ftp_thread_info.FileItem, _ftp_thread_info.SFDLSession.SFDLSessionName, _download_speed, _percent_downloaded)

                        End If

                        ThrottleByteTransfer(_max_bytes_per_second, bytestotalread, _ctime, bytesPerSec)

                    End While

                End Using

            End Using


            If _ftp_thread_info.DownloadStopped = True Then
                _log.Info("Download wurde gestoppt!")
            End If

        Catch ex As FtpCommandException

            If _ftp_thread_info.DownloadStopped = False Then ' Event nur werfen wenn der Download nicht gestoppt ist
                _log.Error(ex, "Fehler beim schreiben der Datei!" & ex.ToString)
                _ftp_thread_info.ErrorOccured = True
                ParseFTPCommandException(ex, _ftp_thread_info)
            End If

        Catch ex As FtpException

            If _ftp_thread_info.DownloadStopped = False Then
                _log.Error(ex, "Fehler beim schreiben der Datei!" & ex.ToString)
                _ftp_thread_info.ErrorOccured = True
                ParseFTPException(ex, _ftp_thread_info)
            End If

        Catch ex As Exception

            If _ftp_thread_info.DownloadStopped = False Then
                _log.Error(ex, "Fehler beim schreiben der Datei!" & ex.ToString)
                _ftp_thread_info.ErrorOccured = True
                ParseException(ex, _ftp_thread_info)
            End If

        Finally

            PostOpenRead(_ftp_thread_info)

        End Try

    End Sub


    Private Sub ThrottleByteTransfer(maxBytesPerSecond As Integer, bytesTotal As Long, elapsed As TimeSpan, bytesPerSec As Integer)
        ' we only throttle if the maxBytesPerSecond is not zero (zero turns off the throttle)
        If maxBytesPerSecond > 0 Then
            ' we only throttle if our through-put is higher than what we want
            If bytesPerSec > maxBytesPerSecond Then
                Dim elapsedMilliSec As Double = If(elapsed.TotalSeconds = 0, elapsed.TotalMilliseconds, elapsed.TotalSeconds * 1000)

                ' need to calc a delay in milliseconds for the throttle wait based on how fast the 
                ' transfer is relative to the speed it needs to be
                Dim millisecDelay As Double = (bytesTotal / (maxBytesPerSecond / 1000) - elapsedMilliSec)

                ' can only sleep to a max of an Int32 so we need to check this since bytesTotal is a long value
                ' this should never be an issue but never say never
                If millisecDelay > 10000 Then
                    millisecDelay = 10000
                End If

                ' go to sleep
                Thread.Sleep(CInt(millisecDelay))
            End If
        End If
    End Sub


    Private Sub PostOpenRead(ByVal _ftp_thread_info As FTPThreadInfo)

        Dim _ftp_client As New FtpClient
        Dim _local_hashing As New Hashing

        Try

            _ftp_client = GetMasterFTPClient(_ftp_thread_info.FTPClientGUID)

            If _ftp_thread_info.ErrorOccured = True Then
                Throw New HashCheckSkipException("Es ist ist zuvor ein Downloadfehler aufgetreten überspringe daher Hash Prüfung")
            End If


            If _ftp_thread_info.DownloadStopped = False Then

                If _ftp_client.HasFeature(FtpCapability.MD5) And _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None Then

                    Dim _reply As FtpReply

                    _log.Info("Server unstützt MD5 Hash - Hole Hash")

                    _reply = _ftp_client.Execute("MD5 {0}", _ftp_thread_info.FileItem.FileFullPath)

                    If _reply.Success Then

                        Dim _tmp_hash As String = String.Empty

                        _log.Info("Hash Serverseitig erfolgreich ermittelt!")

                        _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.MD5
                        _tmp_hash = _reply.Message.Replace(_ftp_thread_info.FileItem.FileFullPath, "")
                        _tmp_hash = _tmp_hash.Replace(Chr(34), "")
                        _ftp_thread_info.FileItem.FileHash = _tmp_hash.Trim

                    Else
                        _log.Error("Hash konnte nicht ermittelt werden!")
                    End If

                End If

                If _ftp_client.HasFeature(FtpCapability.XMD5) And _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None Then

                    Dim _reply As FtpReply

                    _log.Info("Server unstützt MD5 Hash - Hole Hash")

                    _reply = _ftp_client.Execute("XMD5 {0}", _ftp_thread_info.FileItem.FileFullPath)

                    If _reply.Success Then

                        Dim _tmp_hash As String = String.Empty

                        _log.Info("Hash Serverseitig erfolgreich ermittelt!")

                        _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.MD5
                        _tmp_hash = _reply.Message.Replace(_ftp_thread_info.FileItem.FileFullPath, "")
                        _tmp_hash = _tmp_hash.Replace(Chr(34), "")
                        _ftp_thread_info.FileItem.FileHash = _tmp_hash.Trim

                    Else
                        _log.Error("Hash konnte nicht ermittelt werden!")
                    End If

                End If

                If _ftp_client.HasFeature(FtpCapability.XSHA1) And _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None Then

                    Dim _reply As FtpReply

                    _log.Info("Server unstützt SHA1 Hash - Hole Hash")

                    _reply = _ftp_client.Execute("XSHA1 {0}", _ftp_thread_info.FileItem.FileFullPath)

                    If _reply.Success Then

                        Dim _tmp_hash As String = String.Empty

                        _log.Info("Hash Serverseitig erfolgreich ermittelt!")

                        _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.SHA1
                        _tmp_hash = _reply.Message.Replace(_ftp_thread_info.FileItem.FileFullPath, "")
                        _tmp_hash = _tmp_hash.Replace(Chr(34), "")
                        _ftp_thread_info.FileItem.FileHash = _tmp_hash.Trim

                    Else
                        _log.Error("Hash konnte nicht ermittelt werden!")
                    End If

                End If

                If _ftp_client.HasFeature(FtpCapability.XCRC) And _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None Then

                    Dim _reply As FtpReply

                    _log.Info("Server unstützt CRC Hash - Hole Hash")

                    _reply = _ftp_client.Execute("XCRC {0}", _ftp_thread_info.FileItem.FileFullPath)

                    If _reply.Success Then

                        Dim _tmp_hash As String = String.Empty

                        _log.Info("Hash Serverseitig erfolgreich ermittelt!")

                        _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.CRC
                        _tmp_hash = _reply.Message.Replace(_ftp_thread_info.FileItem.FileFullPath, "")
                        _tmp_hash = _tmp_hash.Replace(Chr(34), "")
                        _ftp_thread_info.FileItem.FileHash = _tmp_hash.Trim

                    Else
                        _log.Error("Hash konnte nicht ermittelt werden!")
                    End If

                End If

            End If

        Catch ex As FtpCommandException
            _log.Error(ex, "Hash konnte FTP Seitig nicht ermittelt werden!")
            _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None
            _ftp_thread_info.FileItem.FileHash = String.Empty

        Catch ex As HashCheckSkipException
            _log.Warn(ex, ex.Message)
            _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None
            _ftp_thread_info.FileItem.FileHash = String.Empty

        Catch ex As Exception
            _log.Error(ex, ex.Message)
            _ftp_thread_info.FileItem.FileHashType = File.FileInfo.HashType.None
            _ftp_thread_info.FileItem.FileHash = String.Empty

        End Try

        Try

            If _ftp_thread_info.ErrorOccured = True Then
                Throw New HashCheckSkipException("Es ist ist zuvor ein Downloadfehler aufgetreten überspringe daher Hash Prüfung")
            End If

            If _ftp_thread_info.DownloadStopped = False Then

                Select Case _ftp_thread_info.FileItem.FileHashType

                    Case File.FileInfo.HashType.None

                        UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete)

                    Case File.FileInfo.HashType.MD5

                        _log.Info("MD5 Hash vorhanden - Prüfe")

                        UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileValdilation)

                        If _local_hashing.MD5FileHash(_ftp_thread_info.LocalFileInfo.FullName).ToLower.Equals(_ftp_thread_info.FileItem.FileHash.ToString.ToLower) Then
                            _log.Info("MD5 Hash Valid")
                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashValid)
                        Else
                            _log.Warn("MD5 Hash invalid")
                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashInvalid)
                        End If

                    Case File.FileInfo.HashType.CRC

                        _log.Info("CRC Checksumme vorhanden - Prüfe")

                        UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileValdilation)

                        If _local_hashing.CRC32FileHash(_ftp_thread_info.LocalFileInfo.FullName).ToLower.Equals(_ftp_thread_info.FileItem.FileHash.ToString.ToLower) Then
                            _log.Info("CRC Checksumme Valid")
                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashValid)
                        Else
                            _log.Warn("CRC Checksumme Invalid")
                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashInvalid)
                        End If

                    Case File.FileInfo.HashType.SHA1

                        _log.Info("SHA1 Hash vorhanden - Prüfe")

                        UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.FileValdilation)

                        If _local_hashing.SHA1FileHash(_ftp_thread_info.LocalFileInfo.FullName).ToLower.Equals(_ftp_thread_info.FileItem.FileHash.ToString.ToLower) Then
                            _log.Info("SHA1 Hash Valid")
                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashValid)
                        Else
                            _log.Warn("SHA1 Hash invalid")
                            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashInvalid)
                        End If

                End Select

            End If

        Catch ex As HashCheckSkipException
            _log.Warn(ex, ex.Message)

        Catch ex As Exception
            _log.Error(ex, "Lokaler Datei Hash konnte nicht ermittelt werden!")
            UpdateItem(_ftp_thread_info.FileItem, UpdateItemEventType.DownloadComplete_HashInvalid)

        Finally

            If _ftp_thread_info.ErrorOccured = False Then

                Try 'CleanUp

                    _log.Info("Entferne File {0} mit der GUID {1} aus der Liste...", _ftp_thread_info.FileItem.FileName, _ftp_thread_info.GUID)
                    DecreaseActiveThreadCounter(_ftp_thread_info.SFDLSession.SFDLSessionName)
                    RemoveFTPThreadInfo(_ftp_thread_info)
                Catch ex As Exception
                    _log.Error(ex, ex.Message)
                End Try

            Else
                _log.Info("Entferne File {0} mit der GUID {1} NICHT aus der Liste da zuvor ein Fehler aufgetreten ist!", _ftp_thread_info.FileItem.FileName, _ftp_thread_info.GUID)
            End If

            Dim e As New FileDownloadCompleteEventArg

            e.OLVItem = _ftp_thread_info.FileItem

            RaiseEvent FileDownloadComplete(Me, e)

        End Try

        If _ftp_thread_info.DownloadStopped = False Then

            System.Threading.Thread.Sleep(1000)

            _log.Info("Prüfe auf neue Downloads...")

            CheckForNewThreads()

        End If

    End Sub

    Protected Friend Sub StopThreads()

        Dim _error_occured As Boolean = False
        Dim _update_list As New List(Of FTPThreadInfo)

        Try

            Monitor.Enter(_obj_lock_list_ftpthreadInfo)

            For Each _ftp_thread_info In _list_ftpthreadinfo

                Try

                    Dim _var_sfdl_session As New SFDLSession

                    _log.Info("Stoppe Thread mit GUID {0}", _ftp_thread_info.Value.GUID.ToString)

                    _ftp_thread_info.Value.DownloadStopped = True
                    _ftp_thread_info.Value.SFDLSession.TimeDownloadStopped = Now

                    _update_list.Add(_ftp_thread_info.Value)

                    _var_sfdl_session = GetSFDLSessionByKey(_ftp_thread_info.Value.SFDLSession.SFDLSessionName)
                    _var_sfdl_session.TimeDownloadStopped = Now
                    _var_sfdl_session.SessionState = SFDLSessionEnums.SFDLSessionState.DonwloadStopped

                    UpdateSFDLSession(New KeyValuePair(Of String, SFDLSession)(_var_sfdl_session.SFDLSessionName, _var_sfdl_session))

                Catch ex As Exception
                    _log.Error(ex, "Fehler beim stoppen des Downloads für File {0} mit GUID {1}", _ftp_thread_info.Value.FileItem.FileName, _ftp_thread_info.Value.GUID.ToString)
                    _error_occured = True
                End Try

            Next

            Thread.Sleep(500)

            For Each _item In _update_list
                UpdateItem(_item.FileItem, UpdateItemEventType.DownloadStopped)
            Next

            'Anderen Items die sich in der Warteschlangen befanden ebenfalls zurücksetzten
            For Each _session In GetSFDLSessionList

                For Each _item In _session.Value.OLVItemList

                    If _item.Status = Status.Waiting Then
                        _item.Status = Status.Queued
                        _item.StatusText = String.Empty
                        _item.Progress = 0
                    End If

                Next

            Next

        Catch ex As Exception
            _log.Error(ex, ex.Message)
        Finally

            Monitor.Exit(_obj_lock_list_ftpthreadInfo)

            If _error_occured = False Then
                RaiseEvent StopDownloadComplete(Me, New EventArgs)
            Else
                MessageBox.Show("Download konnte nicht gestoppt werden!", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End If

        End Try

    End Sub

End Class